#ifndef __SINK_HPP__
#define __SINK_HPP__
/**
 * 日志落地类，实现日志的不同输出方式
 * 使用简单工厂模式
 */
#include "Common.hpp"
#include <memory>
#include <cassert>
#include <sstream>

namespace MyLog
{
    class LogSink
    {
    public:
        using LogSinkshptr=std::shared_ptr<LogSink>;
        virtual void log(const char *data, std::size_t len)=0;//日志写入函数
    };

    class StdOutSink : public LogSink
    {
    public:
        using StdOutSinkshptr=std::shared_ptr<StdOutSink>;

        void log(const char *data, std::size_t len)
        {
            //C语言风格的字符串最后不知道是以'\n'还是'\0'结尾，使用<<可能会出现格式错误
            std::cout.write(data,len);
        }
    };

    class FileSink : public LogSink
    {
    public:
        using FileSinkshptr=std::shared_ptr<FileSink>;

        FileSink(const std::string &pathname)
                :_pathname(pathname)
        {
            //直接在创建对象时就创建并打开文件，log函数必定会被频繁调用，
            //如果每次在log函数内都需要进行判断文件是否存在，打开文件，势必会降低效率
            Common::File::CreateDirectory(Common::File::GetRalativePath(_pathname));
            _ofs.open(pathname,std::ios::binary | std::ios::app);//以二进制追加写入方式打开
            assert(_ofs.is_open());
        }

        void log(const char *data, std::size_t len)
        {
            _ofs.write(data, len);
            assert(_ofs.good());
        }

    private:
        std::string _pathname;
        std::fstream _ofs;
    };

    /**
     * 按照文件大小来选择日志信息写入到哪个文件中
     */
    class RollSinkBySize : public LogSink
    {
    public:
        using RollSinkBySizeshptr=std::shared_ptr<RollSinkBySize>;

        RollSinkBySize(const std::string &basename,const std::size_t &maxsize)
                    :_name_count(0),
                     _basename(basename),
                     _current_file_size(0),
                     _max_file_size(maxsize)
        {
            //以basename创建一个目录
            Common::File::CreateDirectory(Common::File::GetRalativePath(_basename));
            std::string fname = CreateFilename();
            _ofs.open(fname.c_str(), std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }

        void log(const char *data, std::size_t len)
        {
            // assert(Common::File::Exists(_basename));
            if(_current_file_size + len >= _max_file_size)
            {
                _ofs.close();//关闭上一个文件，否则会造成资源泄露
                std::string fname= CreateFilename();
                _ofs.open(fname.c_str(),std::ios::binary | std::ios::app);//以二进制追加写入方式打开
                assert(_ofs.is_open());
                _current_file_size=0;
            }
            _ofs.write(data,len);//写入文件
            assert(_ofs.good());//判断是否写入成功
            _current_file_size += len;
        }

    private:
        std::string CreateFilename()
        {
            //获取时间，作为滚动文件的后缀名
            time_t timeStamp = Common::Date::GetNowTimeStamp();
            struct tm tempt;
            localtime_r(&timeStamp, &tempt);

            //将后缀名转输入到流中，最后返回一个字符串
            std::stringstream ss;
            ss << _basename
               << tempt.tm_year +1900
               << tempt.tm_mon + 1
               << tempt.tm_mday
               << tempt.tm_hour
               << tempt.tm_min
               << tempt.tm_sec
               << _name_count++
               << ".log";

            return ss.str();
        }
    private:
        std::size_t _name_count;
        std::string _basename;//基类文件的前缀名，根据_basename+后缀名来区分每一个文件
        std::fstream _ofs;
        std::size_t _current_file_size;//当前文件大小，判断是否大于max文件大小来决定是否滚动文件
        std::size_t _max_file_size;//最大文件大小
    };

    
    enum class TimeGap
    {
        GAP_SECOND,
        GAP_MINUTE,
        GAP_HOUR,
        GAP_DAY
    };
    class RollSinkByTimeGap : public LogSink
    {
    public:
        using FileSinkshptr=std::shared_ptr<RollSinkByTimeGap>;

        RollSinkByTimeGap(const std::string &basename , const TimeGap &timegap)
                :_basename(basename),
                 _name_count(0)
        {
            switch(timegap)
            {
                case TimeGap::GAP_SECOND:_gap_size=1;break;
                case TimeGap::GAP_MINUTE:_gap_size=60;break;
                case TimeGap::GAP_HOUR  :_gap_size=3600;break;
                case TimeGap::GAP_DAY   :_gap_size=3600*24;break;
            }
            //直接在创建对象时就创建并打开文件，log函数必定会被频繁调用，
            //如果每次在log函数内都需要进行判断文件是否存在，打开文件，势必会降低效率
            _cur_gap_size = _gap_size == 1 ?
                            Common::Date::GetNowTimeStamp() : 
                            Common::Date::GetNowTimeStamp() % _gap_size;
            std::string filename = CreateFilename();
            Common::File::CreateDirectory(Common::File::GetRalativePath(filename));
            _ofs.open(filename,std::ios::binary | std::ios::app);//以二进制追加写入方式打开
            assert(_ofs.is_open());
        }

        void log(const char *data, std::size_t len)
        {
            time_t temp = Common::Date::GetNowTimeStamp() % _gap_size;
            if( temp != _cur_gap_size)
            {
                _ofs.close();
                std::string filename = CreateFilename();
                _ofs.open(filename,std::ios::binary | std::ios::app);//以二进制追加写入方式打开
                assert(_ofs.is_open());
                // _cur_gap_size = temp;
            }
            _ofs.write(data, len);
            assert(_ofs.good());
        }
    private:
        std::string CreateFilename()
        {
            //获取时间，作为滚动文件的后缀名
            time_t timeStamp = Common::Date::GetNowTimeStamp();
            struct tm tempt;
            localtime_r(&timeStamp, &tempt);

            //将后缀名转输入到流中，最后返回一个字符串
            std::stringstream ss;
            ss  << _basename
                << tempt.tm_year +1900
                << tempt.tm_mon + 1
                << tempt.tm_mday
                << tempt.tm_hour
                << tempt.tm_min
                << tempt.tm_sec
                <<".log";
            // else ss<<".log";
                
            return ss.str();
        }
    private:
        std::size_t _name_count;
        std::string _basename;
        std::fstream _ofs;
        std::size_t _gap_size;
        std::size_t _cur_gap_size;
        
    };

    //落地工厂类，根据传递参数的不同返回不同的对象，使用模板+不定参数
    class SinkFactory
    {
    public:
        template <typename SinkType, typename ...Args>
        static LogSink::LogSinkshptr CreateSink(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif